/*
 * Resource.java
 *
 * Created on October 20, 2007, 6:52 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package simulationengine.process;

import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 *
 * @author jkeister
 */
public class Resource extends Object implements Comparable<Resource> {
    
    AtomicInteger localTime;
    AtomicInteger depth;
    AtomicInteger lowest;
    AtomicIntegerArray exitTimes;
    AtomicBoolean full;
    
    final boolean DEBUG = true;

    public PriorityBlockingQueue nextAvailableResourceQ;
    Semaphore available;
    CyclicBarrier barrier;
    CyclicBarrier fullStop;
    
    public Resource nextResource;
    //PriorityBlockingQueue nextToExitThreadQ;
    
    final Distribution serviceDistribution;
    final boolean syncExit;
    final boolean noDelay;
    final int type;
    int numResources;    
    
    /** Creates a new instance of Resource */
    //removed resourcesAvailable, assume 1 for binary semaphore
    public Resource(Distribution dist, int resourceType) 
    {
        //final int endTime = end;
        
        serviceDistribution = dist;
        
        localTime = new AtomicInteger(0);
        //previousExitTime = new AtomicInteger(0);
        
        available = new Semaphore(1, true);
        
        syncExit = false;
        
        numResources = 1;
        
        //Should we always have a next resource, say an exitResource is last?
        nextResource = null;
        
        noDelay = false;
        
        type = resourceType;
    }
    
    public Resource(Distribution dist, Resource next, int resourceType) 
    {
        //final int endTime = end;
        
        serviceDistribution = dist;
        
        localTime = new AtomicInteger(0);
        //previousExitTime = new AtomicInteger(0);
        
        available = new Semaphore(1, true);
        
        syncExit = false;
        
        numResources = 1;
        
        //Should we always have a next resource, say an exitResource is last?
        nextResource = next;
        
        noDelay = false;
        
        type = resourceType;
    }
    
    //Resource with subresources
    public Resource(int resourcesAvailable, Resource next, boolean sync, boolean delay) 
    {
        //final int endTime = end;
        numResources = resourcesAvailable;
        
        serviceDistribution = null;
        
        localTime = new AtomicInteger(0);
        //previousExitTime = new AtomicInteger(0);
        
        available = new Semaphore(numResources, true);
        
        nextAvailableResourceQ = new PriorityBlockingQueue(numResources);
        
        syncExit = sync;
        noDelay = delay;
      
        if(resourcesAvailable == 6)
            numResources--;
        
            //CyclicBarrier - trap the right number of threads,
            barrier = new CyclicBarrier(numResources, new Runnable() {
                                   public void run() {
                                     lowest();
                                   }
                                 });
                                 
        if(resourcesAvailable == 6)
            numResources++;                                 
                                 
            fullStop = new CyclicBarrier(numResources, new Runnable() {
                                   public void run() {
                                     lowest();
                                   }
                                 });

            //Array to hold the times competing for exit
            exitTimes = new AtomicIntegerArray(numResources+2);
            
            //Record earliest known departure here
            lowest = new AtomicInteger(0);
            depth = new AtomicInteger(0);
            full = new AtomicBoolean(false);
       
        nextResource = next;
        
        type = 0;
    }
    
    public void record(int myCompletionTime)
    {
        if(full.get())
        {
            System.out.println("Inside fullstop");
            System.out.println(this.available.getQueueLength());
            
            if(depth.get() < numResources+3)
                exitTimes.set(depth.getAndIncrement(),myCompletionTime);
            
            try{ fullStop.await(100,TimeUnit.SECONDS); }
                catch(BrokenBarrierException bbe) 
                { 
                    System.out.println("Interrupted with fullStop barrier");
                    //localTime.set(myCompletionTime*2);
                    //fullStop.reset();
                    return;
                }
                catch(TimeoutException toe) 
                {
                    System.out.println("TimeoutException while trying to wait in fullStop barrier");
                    localTime.set(Integer.MAX_VALUE);
                    return;
                }
                catch(InterruptedException ie)
                {
                    System.out.println("Interrupted in fullStop barrier");
                    //localTime.set(myCompletionTime*2);
                    return;
                }
        }
        else
        {
        if(depth.get() < numResources)
            exitTimes.set(depth.getAndIncrement(),myCompletionTime);
        
        try{ barrier.await(100,TimeUnit.SECONDS); }
            catch(BrokenBarrierException bbe) 
                { 
                    System.out.println("Interrupted with barrier");
                    //localTime.set(myCompletionTime*2);
                    barrier.reset();
                    lowest.set(myCompletionTime);
                    return;
                }
            catch(TimeoutException toe) 
                {
                    System.out.println("TimeoutException while trying to wait in barrier");
                    localTime.set(Integer.MAX_VALUE);
                    return;
                }
            catch(InterruptedException ie)
                {
                    System.out.println("Interrupted in barrier");
                    //localTime.set(myCompletionTime*2);
                    return;
                }
        }
    }
    
    public void lowest()
    {
        int temp = exitTimes.get(0);
        int resourceCount = numResources;
        
        if(full.getAndSet(false))
            resourceCount++;
            
        if(DEBUG)
            System.out.println("Woooooooooooooooooooooooooooooooo lowest of "
                + resourceCount + " !");
        for(int i = 1; i < resourceCount; i++)
            if(temp > exitTimes.get(i))
                temp = exitTimes.get(i);
        
        //                     && !full.get() )?
        
        if(nextResource != null)
            if(nextResource.exitTimes != null && nextResource.barrier.getNumberWaiting() > 0)
                if(nextResource.exitTimes.get(0) != 0 && nextResource.exitTimes.get(0)+52 < temp)
                {
                    System.out.println("Looks like someone COULD cut us in line");
                    full.set(true);
                    depth.set(0);
                    lowest.set(0);
                    nextResource.lowest.set(nextResource.exitTimes.get(0));
                    nextResource.barrier.reset();
                    return;
                }
         
                
        lowest.set(temp);
        if(DEBUG)
            System.out.println("\t\t\t\t get low " + temp);
        depth.set(0);
        
        for(int i = 0; i < exitTimes.length(); i++)
            exitTimes.set(i,Integer.MAX_VALUE);

        return;
        //barrier.reset();
    }
    
    public synchronized int service()
    {
        return serviceDistribution.next();
    }
    
    public int compareTo(Resource r)
    {
        if(r.localTime.intValue() > this.localTime.intValue())
        {
            return -1;
        }
        else if(r.localTime.intValue() == this.localTime.intValue())
        {
            if(r.type > this.type)
                return -1;
            else if(r.type == this.type)
                return 0;
            else
                return 1;
        }
        else
        {
            return 1;
        }
    }
}